
package controller;

import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.ArrayList;

import model.DatabaseModel;
import model.ServerModel;
import utils.Command;
import view.ServerView;

public class ServerController {
    
    private static final int FIU_CLIENTNAME = 0;
    private static final int FIU_FILENAME = 1;
    private static final int FIU_WHERE = 2;
    
    private ServerModel serverModel;
    private ServerView serverView;
    private ArrayList<String> connectedClients;
    private ArrayList<String[]> filesInUse;
    
    private final Object lockClientList = new Object();
    private final Object lockDatabase = new Object();
    private final Object lockFilesList = new Object();
    
    public ServerController(ServerModel serverModel, ServerView serverView) {
        this.serverModel = serverModel;
        this.serverView = serverView;
        connectedClients = new ArrayList<String>();
        filesInUse = new ArrayList<String[]>();
        DatabaseModel.getInstance();
    }
    
    public void start() {
        
        serverModel.startConnection();
        
        AcceptingThread acceptingThread = new AcceptingThread(this);
        acceptingThread.start();
        
        serverView.getUserChoice();
        
        acceptingThread.interrupt();
        serverModel.closeConnection();
        
    }
    
    public void addConnectedClient(String clientName) {
        synchronized (lockClientList) {
            connectedClients.add(clientName);
        }
    }
    
    public void removeConnectedClient(String clientName) {
        synchronized (lockClientList) {
            connectedClients.remove(clientName);
        }
    }
    
    public String[] getConnectedClients() {
        synchronized (lockClientList) {
            String[] result = new String[connectedClients.size()];
            for (int i = 0; i < connectedClients.size(); i++) {
                result[i] = connectedClients.get(i);
            }
            return result;
        }
    }
    
    public boolean addFileInUse(String clientName, String fileName, String where) {
        synchronized (lockFilesList) {
            if (where.equalsIgnoreCase("all")) {
                if (!this.checkIfFileInUse(fileName)) {
                    String[] file = new String[] {
                            clientName, fileName, where
                    };
                    filesInUse.add(file);
                    return true;
                }
            } else {
                if (!this.checkIfFileInUse(fileName, where)) {
                    String[] file = new String[] {
                            clientName, fileName, where
                    };
                    filesInUse.add(file);
                    return true;
                }
            }
            return false;
        }
    }
    
    public void removeFileInUse(String clientName, String fileName, String where) {
        synchronized (lockFilesList) {
            String[] file = new String[] {
                    clientName, fileName, where
            };
            filesInUse.remove(file);
        }
    }
    
    public void removeFileInUse(String clientName, String fileName,
            ObjectOutputStream out) throws IOException {
        synchronized (lockFilesList) {
            for (int i = 0; i < filesInUse.size(); i++) {
                if (filesInUse.get(i)[FIU_FILENAME].equalsIgnoreCase(fileName)
                        && filesInUse.get(i)[FIU_CLIENTNAME]
                                .equalsIgnoreCase(clientName)) {
                    filesInUse.remove(i);
                    break;
                }
            }
            out.writeObject(String.valueOf(Command.OK));
            out.flush();
            out.reset();
        }
    }
    
    public boolean checkIfFileInUse(String fileName, String where) {
        boolean returnValue = false;
        synchronized (lockFilesList) {
            for (int i = 0; i < filesInUse.size(); i++) {
                if (filesInUse.get(i)[FIU_FILENAME].equalsIgnoreCase(fileName)
                        && (filesInUse.get(i)[FIU_WHERE]
                                .equalsIgnoreCase(where) || filesInUse.get(i)[FIU_WHERE]
                                .equalsIgnoreCase("all"))) {
                    returnValue = true;
                }
            }
            return returnValue;
        }
    }
    
    public boolean checkIfFileInUse(String fileName) {
        boolean returnValue = false;
        synchronized (lockFilesList) {
            for (int i = 0; i < filesInUse.size(); i++) {
                if (filesInUse.get(i)[FIU_FILENAME].equalsIgnoreCase(fileName)) {
                    returnValue = true;
                }
            }
            return returnValue;
        }
    }
    
    public void optionCreateFile(String clientName, String fileName,
            ObjectOutputStream out)
            throws IOException {
        synchronized (lockDatabase) {
            if (DatabaseModel.checkIfFileExists(fileName)) {
                out.writeObject(String.valueOf(Command.ERROR));
                out.flush();
                out.reset();
            } else {
                if (addFileInUse(clientName, fileName, "all")) {
                    DatabaseModel.insert(clientName, fileName, "md55555");
                    out.writeObject(String.valueOf(Command.OK));
                    out.flush();
                    out.reset();
                }
            }
        }
    }
    
    public void optionReadFile(String clientName, String fileName,
            ObjectOutputStream out)
            throws IOException {
        String[] connectedClients = this.getConnectedClients();
        boolean unavailable = false;
        synchronized (lockDatabase) {
            if (DatabaseModel.checkIfFileExists(fileName)) {
                if (DatabaseModel.checkIfFileAvailable(fileName,
                        connectedClients)) {
                    String nodeName;
                    if (DatabaseModel.checkIfClientHas(clientName, fileName)) {
                        nodeName = clientName;
                    } else {
                        nodeName = DatabaseModel.getClientAvailable(fileName,
                                connectedClients);
                    }
                    while (!this.addFileInUse(clientName, fileName, nodeName)) {
                        connectedClients = removeItem(connectedClients,
                                nodeName);
                        if (connectedClients.length > 1) {
                            nodeName = DatabaseModel.getClientAvailable(
                                    fileName,
                                    connectedClients);
                        } else {
                            unavailable = true;
                            break;
                        }
                    }
                    if (unavailable) {
                        out.writeObject(String.valueOf(Command.UNAVAILABLE_OP));
                        out.flush();
                        out.reset();
                    } else {
                        if (nodeName.equalsIgnoreCase(clientName)) {
                            nodeName = "local";
                        }
                        out.writeObject(String.valueOf(Command.OK + nodeName));
                        out.flush();
                        out.reset();
                    }
                } else {
                    out.writeObject(String.valueOf(Command.UNAVAILABLE_FILE));
                    out.flush();
                    out.reset();
                }
            } else {
                out.writeObject(String.valueOf(Command.ERROR));
                out.flush();
                out.reset();
            }
        }
    }
    
    private String[] removeItem(String[] stringArray, String item) {
        String[] returnArray;
        if (stringArray.length <= 1) {
            returnArray = new String[] {
                ""
            };
        } else {
            returnArray = new String[stringArray.length - 1];
            int aux = 0;
            for (int i = 0; i < stringArray.length; i++) {
                if (!stringArray[i].equalsIgnoreCase(item)) {
                    returnArray[aux] = stringArray[i];
                    aux++;
                }
            }
        }
        return returnArray;
    }
    
    public void optionDeleteFile(String clientName, String fileName,
            ObjectOutputStream out) throws IOException {
        synchronized (lockDatabase) {
            if (DatabaseModel.checkIfClientHas(clientName, fileName)) {
                if (!checkIfFileInUse(fileName, clientName)) {
                    DatabaseModel.remove(clientName, fileName);
                    out.writeObject(String.valueOf(Command.OK));
                    out.flush();
                    out.reset();
                } else {
                    out.writeObject(String.valueOf(Command.ERROR));
                    out.flush();
                    out.reset();
                }
            } else {
                out.writeObject(String.valueOf(Command.ERROR));
                out.flush();
                out.reset();
            }
        }
    }
    
    public void optionDownloadFile(String clientName, String fileName,
            ObjectOutputStream out) throws IOException {
        String[] connectedClients = this.getConnectedClients();
        boolean unavailable = false;
        synchronized (lockDatabase) {
            if (DatabaseModel.checkIfFileExists(fileName)) {
                if (DatabaseModel.checkIfFileAvailable(fileName,
                        connectedClients)) {
                    String nodeName;
                    if (DatabaseModel.checkIfClientHas(clientName, fileName)) {
                        out.writeObject(String.valueOf(Command.ERROR));
                        out.flush();
                        out.reset();
                    } else {
                        nodeName = DatabaseModel.getClientAvailable(fileName,
                                connectedClients);
                        while (!this.addFileInUse(clientName, fileName,
                                nodeName)) {
                            connectedClients = removeItem(connectedClients,
                                    nodeName);
                            if (connectedClients.length > 0) {
                                nodeName = DatabaseModel.getClientAvailable(
                                        fileName, connectedClients);
                            } else {
                                unavailable = true;
                                break;
                            }
                        }
                        if (unavailable) {
                            out.writeObject(String.valueOf(Command.UNAVAILABLE_OP));
                            out.flush();
                            out.reset();
                        } else {
                            DatabaseModel.insert(clientName, fileName, "555");
                            out.writeObject(String.valueOf(Command.OK
                                    + nodeName));
                            out.flush();
                            out.reset();
                        }
                    }
                } else {
                    out.writeObject(String.valueOf(Command.UNAVAILABLE_FILE));
                    out.flush();
                    out.reset();
                }
            } else {
                out.writeObject(String.valueOf(Command.ERROR));
                out.flush();
                out.reset();
            }
        }
    }
    
    public void optionEditFile(String clientName, String fileName,
            ObjectOutputStream out) throws IOException {
        String[] connectedClients = this.getConnectedClients();
        synchronized (lockDatabase) {
            if (DatabaseModel.checkIfClientHas(clientName, fileName)) {
                if (addFileInUse(clientName, fileName, "all")) {
                    String[] clientsToUpdate = DatabaseModel
                            .getClientsThatHave(fileName, connectedClients);
                    clientsToUpdate = removeItem(clientsToUpdate, clientName);
                    String clientsToUpdateAsString = "";
                    for (int i = 0; i < clientsToUpdate.length; i++) {
                        clientsToUpdateAsString = clientsToUpdateAsString
                                + clientsToUpdate[i];
                        if (i != clientsToUpdate.length - 1) {
                            clientsToUpdateAsString = clientsToUpdateAsString
                                    + ",";
                        }
                    }
                    out.writeObject(String.valueOf(Command.OK
                            + clientsToUpdateAsString));
                    out.flush();
                    out.reset();
                } else {
                    out.writeObject(String.valueOf(Command.ERROR));
                    out.flush();
                    out.reset();
                }
            } else {
                out.writeObject(String.valueOf(Command.ERROR));
                out.flush();
                out.reset();
            }
        }
    }
    
    public void optionDatabaseAdd(String clientName, String fileName,
            ObjectOutputStream out) throws IOException {
        synchronized (lockDatabase) {
            DatabaseModel.insert(clientName, fileName, "md55555");
            out.writeObject(String.valueOf(Command.OK));
            out.flush();
            out.reset();
        }
    }
    
    public void optionDatabaseRemove(String clientName, String fileName,
            ObjectOutputStream out) throws IOException {
        synchronized (lockDatabase) {
            DatabaseModel.remove(clientName, fileName);
            out.writeObject(String.valueOf(Command.OK));
            out.flush();
            out.reset();
        }
    }
    
    private class AcceptingThread extends Thread {
        
        private ServerController serverController;
        
        public AcceptingThread(ServerController serverController) {
            this.serverController = serverController;
        }
        
        @Override
        public void run() {
            while (!isInterrupted()) {
                serverModel.acceptNewClient(serverController);
            }
        }
    }
    
}
